/*
 * Interruptverwaltung.cpp
 *
 *  Created on: 03.05.2011
 *      Author: Jasper
 */

#include "Interruptverwaltung.h"

Interruptverwaltung * Interruptverwaltung::instance;
pthread_mutex_t Interruptverwaltung::lockSingleton = PTHREAD_MUTEX_INITIALIZER;

Interruptverwaltung::Interruptverwaltung()
{
    pthread_mutex_init(&lockLists, NULL);

    sem_init(&waitForInit, 0, 0);

}

int Interruptverwaltung::init()
{
    HWaccess::getInstance();

    running = true;

    if(pthread_create(&pthread, NULL, InterruptverwaltungManagementThreadRoutine, this) != 0)
    {
    	printf("pthreadCreate failed at Interruptverwaltung::init\n");
        instance = NULL;
        running = false;
        return -1;
    }

    if(sem_wait(&waitForInit) != 0)
    {
    	printf("sem_wait failed at Interruptverwaltung::init\n");
        running = false;
        instance = NULL;
        return -1;
    }

    if(instance == NULL)
    {
    	printf("instance == null at Interruptverwaltung::init\n");
        running = false;
        return -1;
    }

    return 0;
}

void * Interruptverwaltung::InterruptverwaltungManagementThreadRoutine(void * arg)
{
    Interruptverwaltung * inst = Interruptverwaltung::instance;
    struct sigevent event;
    int interruptID = 0;

    int thisChannelID = ChannelCreate(0);

    if(thisChannelID == -1)
    {
        //ERROR
        perror("Could not create Channel in ManagementThread");
        inst->running = false;
        return 0;
    }

    int thisConnectionID = ConnectAttach(0, getpid(), thisChannelID, 0, 0);
    if(thisConnectionID == -1)
    {
        //ERROR
        perror("Could not attach Channel in ManagementThread");
        inst->running = false;
        ChannelDestroy(thisChannelID);
        goto EXIT_MANAGEMENT_THREAD;
    }

    SIGEV_PULSE_INIT( &event, thisConnectionID, SIGEV_PULSE_PRIO_INHERIT, ISR_MESSAGE_CODE, 0 );

    if (-1 == ThreadCtl(_NTO_TCTL_IO, 0))
    {
    	printf("Could not get needed rights in ManagementThread\n");
        inst->running = false;
        ConnectDetach(thisConnectionID);
        ChannelDestroy(thisChannelID);
        goto EXIT_MANAGEMENT_THREAD;
    }

    for(int i = 0; i < LISTSIZE; i++)
    {
    	inst->list[i] = NULL;
    }

    if((interruptID = InterruptAttach(IRQ, InterruptverwaltungISR, &event, sizeof(struct sigevent), 0)) == -1)
    {
        // ERROR
    	perror("Could not Attach Interrupt in ManagementThread");
    	inst->running = false;
        ConnectDetach(thisConnectionID);
        ChannelDestroy(thisChannelID);
        goto EXIT_MANAGEMENT_THREAD;
    }

    if(pthread_mutex_lock(HWaccess::getInstance()->getWriteLockDIO()) == -1)
    {
        // ERROR
        perror("Could not Lock DIO-Lock in ManagementThread");
        inst->running = false;
        ConnectDetach(thisConnectionID);
        ChannelDestroy(thisChannelID);
        InterruptDetach(interruptID);
        goto EXIT_MANAGEMENT_THREAD;
    }
    out8(DIO_BASEADDR + DIO_INTERRUPT_ENABLE, ~0x06 & in8(DIO_BASEADDR + DIO_INTERRUPT_ENABLE));
    pthread_mutex_unlock(HWaccess::getInstance()->getWriteLockDIO());

    inst->lastValB = in8(DIO_BASEADDR + DIO_PORTB_OFFSET);
    inst->lastValC = in8(DIO_BASEADDR + DIO_PORTC_OFFSET);
    inst->irqStatus = 0;

    sem_post(&(inst->waitForInit));

    while(inst->running)
    {
        struct _pulse pulse;
        if(MsgReceivePulse(thisChannelID, &pulse, sizeof(_pulse), 0))
        {
            //ERROR
            perror("Error while Receiving Pulse Message in ManagementThread");
            inst->running = false;
            ConnectDetach(thisConnectionID);
            ChannelDestroy(thisChannelID);
            InterruptDetach(interruptID);
            goto EXIT_MANAGEMENT_THREAD;
        }

        if(pulse.code == ISR_MESSAGE_CODE)
        {
            int irqStatus = inst->irqStatus;
            inst->irqStatus = 0;

            pthread_mutex_lock(&(inst->lockLists));
            if(irqStatus & 0b00000010)
            {
                // PORTB
                int reg = in8(DIO_BASEADDR + DIO_PORTB_OFFSET);
                for(int i = 0; i < 8; i++)
                {
                    if((reg & 1<<i) != (inst->lastValB & 1<<i))
                    {
                        InterruptverwaltungSendPulses(inst->list[i]);
                    }
                }
                inst->lastValB = reg;
            }

            if(irqStatus & 0b00001000)
            {
                // PORTC
                int reg = in8(DIO_BASEADDR + DIO_PORTC_OFFSET);
                for(int i = 4; i < 8; i++)
                {
                    if((reg & 1<<i) != (inst->lastValC & 1<<i))
                    {
                        InterruptverwaltungSendPulses(inst->list[i+4]);
                    }
                }
                inst->lastValC = reg;
            }

            pthread_mutex_unlock(&(inst->lockLists));
        }
    }

    EXIT_MANAGEMENT_THREAD:

    Interruptverwaltung::instance = NULL;
    inst->~Interruptverwaltung();

    printf("ManagementThread died\n");

    return 0;
}

int Interruptverwaltung::removeListener(int channelID, pid_t pid, int pin)
{
    int index = -1;

    int _port = (pin >> 8) & 0xFF;
    int _bit = pin & 0xFF;

    if((_port != DIO_PORTB_OFFSET && _port != DIO_PORTC_OFFSET)
            || (_port == DIO_PORTC_OFFSET && (_bit&0xF0) == 0))
    {
    	printf("wrong Port at Interruptverwaltung::removeListener\n");
    	return -1;
    }

    int mask = 1<<((DIO_PORTB_OFFSET == _port)?0:4);
    for(int i = (DIO_PORTB_OFFSET == _port)?0:4; i < 8; i++)
    {
        if((_bit & mask) != 0)
        {
            index = i + ((DIO_PORTC_OFFSET == _port)?4:0);
            break;
        }
        mask = mask << 1;
    }

    if(index == -1)
    {
    	printf("no Pin set at Interruptverwaltung::removeListener\n");
    	return -1;
    }

    pthread_mutex_lock(&lockLists);

    struct listItem * act = list[index];
    struct listItem * prev = NULL;

    while(act != NULL)
    {
        if(act->channelID == channelID && act->pid == pid)
            break;

        prev = act;
        act = act->next;
    }

    // not found in list
    if(act == NULL)
    {
    	printf("Listener not found at Interruptverwaltung::removeListener\n");
    	pthread_mutex_unlock(&lockLists);
    	return -1;
    }

    ConnectDetach(act->connectionID);

    if(prev == NULL)
    {
        // act is listhead
        list[index] = act->next;
    }
    else
    {
        // act is somewhere else
        prev->next = act->next;
    }

    pthread_mutex_unlock(&lockLists);

    free(act);

    return 0;
}

int Interruptverwaltung::addListener(int channelID, pid_t pid, int pin, char messageCode, int messageContent)
{
    int index = -1;

    int _port = (pin >> 8) & 0xFF;
    int _bit = pin & 0xFF;

    if((_port != DIO_PORTB_OFFSET && _port != DIO_PORTC_OFFSET)
            || (_port == DIO_PORTC_OFFSET && (_bit&0xF0) == 0))
    {
    	printf("wrong Port at Interruptverwaltung::addListener\n");
    	return -1;
    }

    int mask = 1<<((DIO_PORTB_OFFSET == _port)?0:4);
    for(int i = (DIO_PORTB_OFFSET == _port)?0:4; i < 8; i++)
    {
        if((_bit & mask) != 0)
        {
            index = i + ((DIO_PORTC_OFFSET == _port)?4:0);
            break;
        }
        mask = mask << 1;
    }

    if(index == -1)
    {
    	printf("no Pin set at Interruptverwaltung::addListener\n");
    	return -1;
    }

    listItem * toAdd = (listItem *) malloc(sizeof(listItem));

    toAdd->next = NULL;
    toAdd->channelID = channelID;
    toAdd->connectionID = -1;
    toAdd->messageCode = messageCode;
    toAdd->messageContent = messageContent;
    toAdd->pin = pin;
    toAdd->pid = pid;

    if(pthread_mutex_lock(&lockLists)==-1)
    {
    	printf("mutexLock failed at Interruptverwaltung::addListener\n");
        free(toAdd);
        return -1;
    }

    toAdd->next = list[index];
    list[index] = toAdd;
    pthread_mutex_unlock(&lockLists);

    return 0;
}

const struct sigevent * Interruptverwaltung::InterruptverwaltungISR(void *arg, int id)
{
    Interruptverwaltung::instance->irqStatus |= in8(DIO_BASEADDR + DIO_CLR_IRQ_READ);
    //out8(DIO_BASEADDR + DIO_PORTA_OFFSET, 64);

    struct sigevent * sigevent = (struct sigevent *)arg;

    if(Interruptverwaltung::instance->irqStatus == 0)
        return NULL;
    else
        return sigevent;

}

void Interruptverwaltung::InterruptverwaltungSendPulses(struct listItem * first)
{
    while(first != NULL)
    {
        if(first->connectionID == -1)
            first->connectionID = ConnectAttach(0, first->pid, first->channelID, 0, 0);

    	if(first->connectionID != -1)
    	{
    		MsgSendPulse(first->connectionID, SIGEV_PULSE_PRIO_INHERIT, first->messageCode, first->messageContent);
    	}
        first = first->next;
    }
}

void delRec(struct listItem * first)
{
    if(first->next != NULL)
        delRec(first->next);

    if(first->connectionID != -1)
        ConnectDetach(first->connectionID);

    free(first);
}

Interruptverwaltung::~Interruptverwaltung()
{
    for(int i = 0; i < LISTSIZE; i++)
    {
        delRec(list[i]);
        list[i] = NULL;
    }

    sem_destroy(&waitForInit);
    pthread_mutex_destroy(&lockLists);
}

Interruptverwaltung * Interruptverwaltung::getInstance(){
    if(!instance){
        if(pthread_mutex_lock(&lockSingleton) != 0)
        {
        	printf("mutexLock failed at Interruptverwaltung::getInstance\n");
            return NULL;
        }
        if (!instance){
            instance = new Interruptverwaltung();
            instance->init();
        }
        if (pthread_mutex_unlock(&lockSingleton) != 0)
        {
        	printf("mutexUnlock failed at Interruptverwaltung::getInstance\n");
            return NULL;
        }
    }
    return instance;
}
